#ifndef _DESCRIPTOR_H_
#define _DESCRIPTOR_H_

#include <any>
#include <unordered_map>
#include <optional>
#include <vector>
#include <functional>

namespace SEngine
{
    class AnyDescriptor
    {
    public:
        virtual ~AnyDescriptor() = default;

        template <typename T, typename... Args>
        void AddDescriptor(Args&&... args);
        template<typename T>
        std::optional<std::reference_wrapper<T>> GetDescriptor();
        void Clear() { m_table.clear(); }
        
    private:
        std::unordered_map<size_t, std::any> m_table;
    };
    
    template <typename T, typename... Args>
    inline void AnyDescriptor::AddDescriptor(Args&&... args)
    {
        if (m_table.find(typeid(T).hash_code()) != m_table.end()) {
            m_table.erase(typeid(T).hash_code());
        }
        // m_table.emplace(typeid(T).hash_code(), std::make_any<T>(std::move(t)));
        m_table.emplace(typeid(T).hash_code(), std::make_shared<T>(std::forward<Args>(args)...));
    }
    
    template <typename T>
    inline std::optional<std::reference_wrapper<T>> AnyDescriptor::GetDescriptor()
    {
        if (m_table.find(typeid(T).hash_code()) != m_table.end()) {
            return *std::any_cast<std::shared_ptr<T>>(m_table[typeid(T).hash_code()]);
        }
        return std::nullopt;
    }

    template<typename Base>
    class Descriptor
    {
    public:
        virtual ~Descriptor() = default;

        template<typename T>
        T & AddDescriptor(std::unique_ptr<T> && t);
        template<typename T>
        std::optional<std::reference_wrapper<T>> GetDescriptor();

        void Foreach(std::function<void(Base & elem)> && fn);

        void Clear() { m_tableQueue.clear(); m_table.clear(); }

    private:
        std::vector<Base*> m_tableQueue;
        std::unordered_map<size_t, std::unique_ptr<Base>> m_table;
    };
    
    template <typename Base>
    template <typename T>
    inline T & Descriptor<Base>::AddDescriptor(std::unique_ptr<T> &&t)
    {
        if (m_table.find(typeid(T).hash_code()) != m_table.end()) {
            m_tableQueue.erase(
                std::remove_if(m_tableQueue.begin(), m_tableQueue.end(), [this](Base * item) { return m_table.at(typeid(T).hash_code()).get() == item; }),
                m_tableQueue.end()  
            );
            m_table.erase(typeid(T).hash_code());
        }
        m_tableQueue.emplace_back(t.get());
        m_table.emplace(typeid(T).hash_code(), std::move(t));
        return *t;
    }
    
    template <typename Base>
    template <typename T>
    inline std::optional<std::reference_wrapper<T>> Descriptor<Base>::GetDescriptor()
    {
        if (m_table.find(typeid(T).hash_code()) != m_table.end()) {
            return std::ref(*dynamic_cast<T*>(m_table[typeid(T).hash_code()].get()));
        }   
        return std::nullopt;
    }
    
    template <typename Base>
    inline void Descriptor<Base>::Foreach(std::function<void(Base &elem)> &&fn)
    {
        for (auto elem : m_tableQueue) {
            fn(*elem);
        }
    }
} // namespace SEngine

#endif